package dian.qing.li.demo.security.utils;

import com.google.common.collect.Lists;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@UtilityClass
public class ObjUtils {

    /**
     * 对象深度克隆
     *
     * @param obj 源对象
     * @Author: liqingdian
     */
    public static <T extends Serializable> T clone(T obj) throws Exception {
        // 说明：调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源，这一点不同于对外部资源（如文件流）的释放
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();
    }

    /**
     * 对象转map
     *
     * @param obj 序列化对象
     * @Author: liqingdian
     */
    public static <T> Map<String, Object> toMap(T obj) {
        Assert.isNotNull(obj, "[obj]不能为空");
        Map<String, Object> data = new HashMap<>(16);
        PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(obj.getClass());
        for (PropertyDescriptor property : propertyDescriptors) {
            Method readMethod = property.getReadMethod();
            String name = property.getName();
            if (readMethod != null) {
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                try {
                    Object value = readMethod.invoke(obj);
                    data.put(name, value);
                } catch (IllegalAccessException e) {
                    log.error(e.getMessage(), e);
                } catch (InvocationTargetException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return data;
    }

    /**
     * 非空校验
     *
     * @param obj    对象
     * @param blank  字符串类型是否校验blank
     * @param fields 需要校验的字段，为空，校验全属性
     * @Author: liqingdian
     */
    public static <T> void isNotNull(T obj, boolean blank, String... fields) {
        Map<String, Object> data = toMap(obj);
        Set<String> fieldSet;
        if (ArrayUtils.isNotEmpty(fields)) {
            fieldSet = Lists.newArrayList(fields).stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        } else {
            fieldSet = data.keySet();
        }
        Object value;
        for (String field : fieldSet) {
            value = data.get(field);
            if (value instanceof String && blank) {
                Assert.isNotBlank((String) value, StringUtils.join("[", field, "]不能为空"));
            } else {
                Assert.isNotNull(value, StringUtils.join("[", field, "]不能为空"));
            }
        }
    }

    /**
     * 指定字段trim
     *
     * @param obj    DTO对象
     * @param fields 指定的字段，不指定则全字段操作
     * @author: liqingdian
     */
    public static <T> void trim(T obj, String... fields) {
        Assert.isNotNull(obj, "[obj]不能为空");
        List<String> fieldList;
        if (ArrayUtils.isNotEmpty(fields)) {
            fieldList = Lists.newArrayList(fields);
        } else {
            fieldList = Lists.newArrayList();
        }
        boolean trim = fieldList.size() == 0;
        PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(obj.getClass());
        for (PropertyDescriptor property : propertyDescriptors) {
            Method readMethod = property.getReadMethod();
            Method writeMethod = property.getWriteMethod();
            String name = property.getName();
            if (readMethod != null) {
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                try {
                    Object value = readMethod.invoke(obj);
                    if (value != null && value instanceof String && (trim || fieldList.contains(name))) {
                        value = ((String) value).trim();
                        if (writeMethod != null) {
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(obj, value);
                        }
                    }
                } catch (IllegalAccessException e) {
                    log.error(e.getMessage(), e);
                } catch (InvocationTargetException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }
}
